using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;
using ClimbTracker.BusinessLogic.Entities;
using Emgu.CV;
using Emgu.Util;
using System.Threading;
using ClimbTracker.GUI.Utils;
using System.IO;
using System.Collections;

namespace ClimbTracker.GUI.TabControls
{
    public partial class TrackingTabControl : UserControl
    {
        private Capture _capture;
        private Thread _captureThread;
        private MotionHistory _motionHistory;

        private List<Holding> holdList;
        private int maxHold = -1;

        private Image<Bgr, Byte> image;
        private double rotateAngle = 270.0;

        private bool hasRoute = false;
        private bool hasCompetitor = false;
        private bool _readyToRun = false;
        private bool _isRunning = false;
        private bool _isFinished = false;
        private bool isAborted = false;
        private bool isLive = false;
        private bool hasStarted = false;

        private bool readyToRun
        {
            get { return _readyToRun; }
            set
            {
                _readyToRun = value;
                if (value)
                {
                    startButton.Enabled = true;
                    stopButton.Enabled = false;
                    refreshImage();
                }
                else
                {
                    startButton.Enabled = false;
                    stopButton.Enabled = false;
                }
            }
        }

        private bool isRunning
        {
            get { return _isRunning; }
            set
            {
                _isRunning = value;
                if (value)
                {
                    stopButton.Enabled = true;
                    startButton.Enabled = false;
                }
                else
                {
                    stopButton.Enabled = false;
                    startButton.Enabled = readyToRun;
                }
            }
        }

        private bool isFinished
        {
            get { return _isFinished; }
            set
            {
                _isFinished = value;
                if (value)
                {
                    stopButton.Enabled = true;
                    startButton.Enabled = false;
                }
            }
        }


        private bool drawMotionMarks = true;
        private bool drawRopeMarks = true;
        private bool drawHoldMarks = true;
        private bool drawBounderies = true;

        private bool advancedAlgorithm = false;

        private bool enforceBounderies = true;
        private int rightBound;
        private int leftBound;
        private int bouderyPad = 20;
        private bool isROI;

        private DateTime startTime;
        private DateTime stopTime;
        private TimeSpan pauseTime = new TimeSpan(0);
        private TimeSpan delta;
        private int minConstraint;
        private TimeSpan timeConstraint;
        private bool hasDisqualified;

        private SimpleMovingAverage velocity = new SimpleMovingAverage(7);

        private CircularList<float> heightList = new CircularList<float>(3);
        private CircularList<TimeSpan> timeList = new CircularList<TimeSpan>(3);

        private DateTime lastFall = new DateTime(0);
        
        private Route selectedRoute;
        private Competitor selectedCompetitor;
        private CompetitorsTabControl competitorsTabControl;

        public TrackingTabControl()
        {
            InitializeComponent();

            /*_motionHistory = new MotionHistory(
                6, //number of images to store in buffer, adjust it to fit your camera's frame rate
                20, //0-255, the amount of pixel intensity change to consider it as motion pixel
                1.0, //in second, the duration of motion history you wants to keep
                0.05, //in second, parameter for cvCalcMotionGradient
                0.5); //in second, parameter for cvCalcMotionGradient
            */
            holdList = new List<Holding>();
            //_captureThread = new Thread(ProcessImage);
            _captureThread = new Thread(new ThreadStart(threadMainloop));
            _captureThread.Start();
        }

        public void onTabSelected()
        {
            if (isRunning)
            {
                return;
            }

            // Get the selected competitor and update the labels with his\her details
            selectedCompetitor = CompetitorsTabControl.SelectedCompetitor;
            hasCompetitor = (selectedCompetitor != null  &&  selectedCompetitor.Name != null);
            if (hasCompetitor)
            {
                competitorNameContentLabel.Text = selectedCompetitor.Name;
                ageContentLabel.Text = selectedCompetitor.Age.ToString();
                nationalityContentLabel.Text = selectedCompetitor.Nationality;
                categoryContentLabel.Text = selectedCompetitor.Category;
                orderContentLabel.Text = selectedCompetitor.Ranking.ToString();

                if (File.Exists(selectedCompetitor.PicturePath))
                {
                    competitorPictureBox.Load(selectedCompetitor.PicturePath);
                }
            }
            else
            {
                UpdateText("Competitor has not been chosen yet.");
            }

            // Get the selected route and update the labels its' details
            selectedRoute = RoutesTabControl.SelectedRoute;
            
            hasRoute = (selectedRoute != null && selectedRoute.Name != null);
            if (hasRoute)
            {
                routeNameContentLabel.Text = selectedRoute.Name;
                difficultyContentLabel.Text = selectedRoute.Difficulty.ToString();
                maxScoreContentLabel.Text = selectedRoute.MaxScore.ToString();
                timeConstraintContentLabel.Text = selectedRoute.TimePeriodMinutes.ToString();

                if (File.Exists(selectedRoute.PicturePath))
                {
                    _capture = new Capture(selectedRoute.PicturePath);
                    isLive = false;
                }
                else
                {
                    // Using default camara (live)
                    _capture = new Capture();
                    isLive = true;
                }

                rotateAngle = selectedRoute.RotationAngle;
                if (selectedRoute.LeftBoundary > 0 && selectedRoute.RightBoundary > selectedRoute.LeftBoundary)
                {
                    rightBound = selectedRoute.RightBoundary;
                    leftBound = selectedRoute.LeftBoundary;
                }
                else
                {
                    rightBound = leftBound = -1;
                }

                if (selectedRoute.TimePeriodMinutes > 0)
                {
                    minConstraint = selectedRoute.TimePeriodMinutes;
                    timeConstraint = TimeSpan.FromMinutes(minConstraint);
                }
                else
                {
                    minConstraint = 0;
                    timeConstraint = TimeSpan.Zero;
                }

                updateHoldList();
            }

            readyToRun = (hasRoute && hasCompetitor &&
                          _capture != null && getFrame());

            drawHoldMarks = showHoldMarksCheckBox.Checked;
            drawMotionMarks = showMovementCheckBox.Checked;
            drawRopeMarks = showRopeCheckBox.Checked;
            drawBounderies = showBounderiesCheckBox.Checked;

            restartClimb();
        }

        public void setCompetitorTab(CompetitorsTabControl tab)
        {
            competitorsTabControl = tab;
        }

        private void restartClimb()
        {
            hasDisqualified = false;
            isRunning = false;
            hasStarted = false;
            isFinished = false;
            pauseTime = new TimeSpan(0);
            EventsLogTextBox.Text = "";
            _motionHistory = new MotionHistory(
                6, //number of images to store in buffer, adjust it to fit your camera's frame rate
                20, //0-255, the amount of pixel intensity change to consider it as motion pixel
                1.0, //in second, the duration of motion history you wants to keep
                0.05, //in second, parameter for cvCalcMotionGradient
                0.5); //in second, parameter for cvCalcMotionGradient
        }

        private void updateHoldList()
        {
            holdList.Clear();
            foreach (HotSpot hs in RoutesTabControl.HotspotControlsList)
            {
                WallInstallation wi = hs.WallInstallation;
                holdList.Add(new Holding(wi.XCoordinate, wi.YCoordinate, 30, 30));
            }

            maxHold = -1;
        }

        private void refreshImage()
        {
            capturedImageBox.Image = image;
        }

        private void startButton_Click(object sender, EventArgs e)
        {
            System.Diagnostics.Debug.Assert(readyToRun);

            isRunning = true;

            if (!hasStarted)
            {
                startTime = DateTime.Now;
                UpdateText("Climbing Started at " + startTime.ToLongTimeString());
                hasStarted = true;
            }
            else
            {
                pauseTime += DateTime.Now - stopTime;
            }
            
            _captureThread.Interrupt();
        }

        private void stopButton_Click(object sender, EventArgs e)
        {
            System.Diagnostics.Debug.Assert(isRunning);

            isRunning = false;
            stopTime = DateTime.Now;

            _captureThread.Interrupt();
        }

        public bool getFrame()
        {
            try
            {
                //image = _capture.QueryFrame();//.PyrDown().PyrUp(); //reduce noise from the image
                //image = _capture.QueryFrame();//.PyrDown().PyrUp(); //reduce noise from the image
                image = _capture.QueryFrame().PyrDown().PyrUp(); //reduce noise from the image


                if (rotateAngle != 0.0)
                {
                    image = image.Rotate(rotateAngle, new Bgr(Color.Black), false);
                }

            }
            catch (NullReferenceException ex)
            {
                return false;
            }

            return true;
        }

        public void abortRunning()
        {
            isAborted = true;
            _captureThread.Interrupt();
        }

        private void threadMainloop()
        {
            while (!isAborted)
            {
                try
                {
                    while (!readyToRun && !isRunning)
                    {
                        Thread.Sleep(Timeout.Infinite);
                    }

                    while (isRunning)
                    {
                        ProcessImage();
                    }
                }
                catch (ThreadInterruptedException ex)
                {
                    //Console.WriteLine("Interrupted");
                }
            }
        }

        private float curYAvg = 0;
        private float prevYAvg = 0;

        public void ProcessImage()
        {
            if (!getFrame())
            {
                if (!isFinished)
                {
                    disqualifyClimber();
                }

                isAborted = true;
                return;
            };

            if (!isFinished)
            {
                UpdateTimer();

                //update the motion history
                Image<Gray, Byte> gray = image.Convert<Gray, Byte>();

                if (leftBound == -1 || rightBound == -1)
                {
                    enforceBounderies = false;
                    rightBound = image.Width - 1;
                    enforceBounderies = false;
                }
                else
                {
                    try
                    {
                        gray.ROI = new Rectangle<double>(leftBound - bouderyPad, rightBound + bouderyPad, image.Height - 1, 0);
                        enforceBounderies = activateBordersCheckBox.Checked;
                        isROI = true;
                    }
                    catch (Exception ex)
                    {
                        leftBound = 0;
                        rightBound = image.Width - 1;
                        enforceBounderies = false;
                    }
                }

                _motionHistory.Update(gray);
            }

            #region go over hold marks
            for (int i = 0; i < holdList.Count; ++i)
            {
                Holding hold = holdList[i];

                // Draw markings
                if (drawHoldMarks)
                {
                    image.Draw(new Rectangle<int>(hold.m_rect), new Bgr(hold.m_mark ? Color.Red : Color.Blue), 2);
                }

                #region advanced_algorithm
                if (!isFinished && advancedAlgorithm)
                {
                    if (hold.m_mark) continue;

                    double angle, motionPixelCount;
                    _motionHistory.MotionInfo(hold.m_rect, out angle, out motionPixelCount);
                    if (motionPixelCount > 50)
                    {
                        if ((angle > 340 || angle < 20) || (angle > 160 && angle < 200))
                        {
                            // rope
                            hold.m_count = 0;
                        }
                        else
                        {
                            hold.m_count++;
                            if (hold.m_count > 3)
                            {
                                UpdateText(angle.ToString());
                                holdingWasMarked(i);
                            }
                        }
                    }
                    if (motionPixelCount < 10)
                    {
                        hold.m_count = 0;
                    }
                }
                #endregion
            }
            #endregion

            if (drawBounderies)
            {
                image.Draw(new LineSegment2D<int>(new Point2D<int>(leftBound, 0), new Point2D<int>(leftBound, 720)), new Bgr(Color.Aqua), 2);
                image.Draw(new LineSegment2D<int>(new Point2D<int>(rightBound, 0), new Point2D<int>(rightBound, 720)), new Bgr(Color.Aqua), 2);
            }

            if (!isFinished)
            {
                Seq<MCvConnectedComp> motionComponents = _motionHistory.MotionComponents;

                int sumY = 0;
                int count = 0;
                int maxArea = 0;

                #region go over motion components
                //iterate through each of the motion component
                foreach (MCvConnectedComp acomp in motionComponents)
                {
                    MCvConnectedComp comp = acomp;

                    if (isROI)
                    {
                        comp.rect.x += leftBound - bouderyPad;
                    }

                    // reject the components that have small area (noise)
                    if (comp.rect.Area < 100) continue;

                    // reject the components that have small width (rope)
                    if (comp.rect.width < 10) continue;

                    // Drop the rope components
                    if (comp.rect.height > comp.rect.width * 2.0)
                    {
                        if (drawRopeMarks)
                        {
                            image.Draw(new Rectangle<int>(comp.rect), new Bgr(Color.Green), 2);
                        }
                        continue;
                    }

                    // find the angle and motion pixel count of the specific area
                    double angle, motionPixelCount;
                    _motionHistory.MotionInfo(acomp.rect, out angle, out motionPixelCount);

                    // reject the area that contains too few motion
                    if (motionPixelCount / comp.area < 0.05) continue;

                    // reject the components that are out of bounds
                    if (enforceBounderies)
                    {
                        if ((comp.rect.x < leftBound) || (comp.rect.x + comp.rect.width > rightBound))
                        {
                            disqualifyClimber();
                            break;
                        }
                    }

                    // Draw each individual motion in yellow
                    if (drawMotionMarks)
                    {
                        image.Draw(new Rectangle<int>(comp.rect), new Bgr(Color.Yellow), 2);
                    }

                    sumY += comp.rect.y;
                    count++;
                    maxArea = (comp.rect.Area > maxArea) ? comp.rect.Area : maxArea;

                    if (!advancedAlgorithm)
                    {
                        for (int i = maxHold + 1; i < holdList.Count; ++i)
                        {
                            Holding hold = holdList[i];
                            if ((!hold.m_mark) &&
                                (comp.rect.x + 5 > hold.m_rect.x) &&
                                (comp.rect.x + comp.rect.width - 5 < hold.m_rect.x + hold.m_rect.width) &&
                                (comp.rect.y + 5 > hold.m_rect.y) &&
                                (comp.rect.y + comp.rect.height - 5 < hold.m_rect.y + hold.m_rect.height))
                            {
                                holdingWasMarked(i);

                                break;
                            }
                        }
                    }
                }
                #endregion

                if (count >= 6)
                {
                    float curHeight, prevHeight;
                    TimeSpan curTime, prevTime;
                    heightList.Value = curHeight = sumY / count;
                    timeList.Value = curTime = delta;
                    heightList.Next();
                    timeList.Next();
                    prevHeight = heightList.Value;
                    prevTime = timeList.Value;

                    float speed = (float)((prevHeight - curHeight) * 1000 / (curTime.TotalMilliseconds - prevTime.TotalMilliseconds));
                    velocity.AddSample(speed);

                    if (velocity.Average < -50 && maxArea > 5000 && velocity.totalSamples > 7)
                    {
                        //UpdateText("Fall!!! " + velocity.Average.ToString() + ", " + maxArea.ToString());

                        // Wait 15 sec between falls
                        TimeSpan skip = (DateTime.Now - lastFall);
                        if (skip.TotalSeconds > 15)
                        {
                            lastFall = DateTime.Now;

                            climberFell();

                            if (InvokeRequired)
                            {
                                this.Invoke(new CallDelegate(stopButton.PerformClick));
                            }
                            else
                            {
                                stopButton.PerformClick();
                            }
                        }
                    }
                }
            }

            refreshImage();

            //GC.Collect();
        }

        private void holdingWasMarked(int holdIdx)
        {
            holdList[holdIdx].m_mark = true;
            UpdateText("Competitor Grabbed Hold No. " + (holdIdx + 1).ToString());
            maxHold = holdIdx;
            if (maxHold == holdList.Count - 1)
            {
                finishClimb();
            }
        }

        delegate void CallDelegate();

        private void finishClimb()
        {
            if (InvokeRequired)
            {
                this.Invoke(new CallDelegate(finishClimb));
            }
            else
            {
                UpdateText("Route completed at " + DateTime.Now.ToLongTimeString());

                isFinished = true;
                updateScore();
            }
        }

        private void disqualifyClimber()
        {
            if (InvokeRequired)
            {
                this.Invoke(new CallDelegate(disqualifyClimber));
            }
            else
            {
                hasDisqualified = true;
                UpdateText("Climber Disqualified");
                UpdateText("Attempt Ended at " + DateTime.Now.ToLongTimeString());

                isFinished = true;
                
                updateScore();
            }
        }

        private void climberFell()
        {
            if (InvokeRequired)
            {
                this.Invoke(new CallDelegate(climberFell));
            }
            else
            {
                UpdateText("Climber Fell");
                UpdateText("Attempt Ended at " + DateTime.Now.ToLongTimeString());

                //isFinished = true;
                //readyToRun = false;
                updateScore();
            }
        }

        private void updateScore()
        {
            if (InvokeRequired)
            {
                this.Invoke(new CallDelegate(updateScore));
            }
            else
            {
                int score = (maxHold + 1);
                UpdateText("Competitor Score: " + score.ToString());
                competitorsTabControl.UpdateCompetitorScore(score);
            }
        }

        delegate String SampleDelegate();

        private String UpdateTimer()
        {
            if (InvokeRequired)
            {
                return (String)this.Invoke(new SampleDelegate(UpdateTimer));
            }
            else
            {
                delta = (DateTime.Now - startTime - pauseTime);
                String timer = String.Format("{0}:{1:00}.{2:0}", delta.Minutes, delta.Seconds, (int)(delta.Milliseconds/100.0));
                timerLabel.Text = "Timer : " + timer;
                if (minConstraint > 0)
                {
                    timer2Label.Text = "Time Limit: " + minConstraint.ToString() + " Minutes";
                    if (delta > timeConstraint && !hasDisqualified)
                    {
                        disqualifyClimber();
                    }
                }

                return timer;
            }
        }

        private void UpdateText(String text)
        {
            if (InvokeRequired)
            {
                this.Invoke(new Action<String>(UpdateText), new object[] { text });
            }
            else
            {
                TimeSpan delta = (DateTime.Now - startTime);
                DateTime deltaTime = new DateTime(delta.Ticks);
                EventsLogTextBox.AppendText(UpdateTimer() + " :  " + text + Environment.NewLine);
                EventsLogTextBox.ScrollToCaret();
            }
        }

        private void showHoldMarksCheckBox_CheckedChanged(object sender, EventArgs e)
        {
            drawHoldMarks = showHoldMarksCheckBox.Checked;
        }

        private void showMovementCheckBox_CheckedChanged(object sender, EventArgs e)
        {
            drawMotionMarks = showMovementCheckBox.Checked;
        }

        private void showRopeCheckBox_CheckedChanged(object sender, EventArgs e)
        {
            drawRopeMarks = showRopeCheckBox.Checked;
        }

        private void showBounderiesCheckBox_CheckedChanged(object sender, EventArgs e)
        {
            drawBounderies = showBounderiesCheckBox.Checked;
        }

        private void generateReportButton_Click(object sender, EventArgs e)
        {
            ClimbTracker.Reports.ClimbAttemptReportForm repform = new ClimbTracker.Reports.ClimbAttemptReportForm();
            repform.Show();
        }
    }
}
